package netengine;
 
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;

import main.Game;

import org.apache.log4j.Logger;
 
public class NetSocketHandler 
{
	static Logger logger = Logger.getLogger(Game.class);
	
	public NetSocketHandler(SocketChannel channel)
	{ 
		isClosedByPeer	= false;
		isException     = false; 
		selectionKey    = null;
		socketChannel   = channel; 
		readByteBuffer  = ByteBuffer.allocate(1024 * 8);
		writeByteBuffer = ByteBuffer.allocate(1024 * 4);
	}
	
	public void handleRead()
	{
		synchronized(readByteBuffer)
		{
			try
			{
				int readByteCount = socketChannel.read(readByteBuffer);
				if (readByteCount == -1)
				{
					isClosedByPeer = true;
					return;
				} 
			} 
			catch (Exception exception)
			{
				isException = true;
				logger.error("Exception: ", exception);
			}
		}
	}
	
	public void handleWrite()
	{
		synchronized(writeByteBuffer)
		{
			writeByteBuffer.flip();
			  
			try
			{
				socketChannel.write(writeByteBuffer);
				writeByteBuffer.compact();
			
				if (! writeByteBuffer.hasRemaining())
				{
					int interestOps = selectionKey.interestOps();
					selectionKey.interestOps(interestOps & (~SelectionKey.OP_WRITE));
				}
			}
			catch(IOException exception)
			{
				isException = true; 
				logger.error("Exception: ", exception);
			} 
		}
	}
	
	public void setSelectionkey(SelectionKey key)
	{
		selectionKey = key;
	}
	
	public byte[] readData()
	{
		synchronized(readByteBuffer)
		{
			try
			{
				readByteBuffer.flip();
			  	
				if (readByteBuffer.remaining() < 4)
				{
					readByteBuffer.compact();
					return null;
				}
			
				int length = readByteBuffer.getInt();
			
				readByteBuffer.putInt(0, length);
				readByteBuffer.position(0);
			
				if (readByteBuffer.remaining() < (length + 4))
				{ 	
					readByteBuffer.compact();
					return null;
				}
			
				byte[] data = new byte[length + 4];
				
				readByteBuffer.get(data);
			
				readByteBuffer.compact();
			
				return data;
			}
			catch(Exception exception)
			{
				isException = true;
				logger.error("Exception: ", exception);
			}
		}
		
		return null;
	}
	
	public void writeData(ByteBuffer buffer)
	{
		synchronized(writeByteBuffer)
		{
			try
			{
				writeByteBuffer.flip();
				
				if (! writeByteBuffer.hasRemaining())
				{
					socketChannel.write(buffer);
				}
				
				writeByteBuffer.compact();
				
				if (buffer.hasRemaining())
				{
					writeByteBuffer.put(buffer);
					
					int interestOps = selectionKey.interestOps();
					selectionKey.interestOps(interestOps | SelectionKey.OP_WRITE);
				}
			}
			catch(BufferOverflowException overflowException)
			{
				isException = true;
				logger.warn("write data to buffer error, because buffer overflow."); 
			}
			catch(Exception exception)
			{
				isException = true;
				logger.error("Exception: ", exception);
			} 
		}
	}
	 
	public boolean isException()
	{
		return isException;
	}
	
	public boolean isClosed()
	{
		return isClosedByPeer;
	}
	 
	public int readableDataSize()
	{
		int dataSize = 0;
		
		synchronized (readByteBuffer)
		{
			readByteBuffer.flip();
			dataSize = readByteBuffer.remaining();
			readByteBuffer.compact();
		}
		
		return dataSize;
	}
	
	public void close()
	{
		selectionKey.cancel();
		
		Socket socket            = socketChannel.socket(); 
		InetAddress inetAddress  = socket.getInetAddress();
		
		logger.info("close socket: remote ip = " + inetAddress.getHostAddress() 
				  + ", remote port = " + socket.getPort());
		
		try
		{
			socketChannel.close();
		}
		catch(Exception exception)
		{
			logger.error("Exception: ", exception);
		}
	}
	
	private SelectionKey  selectionKey;
	private SocketChannel socketChannel; 
	private boolean       isClosedByPeer;
	private boolean		  isException;
	private ByteBuffer    readByteBuffer;
	private ByteBuffer    writeByteBuffer;
}
